home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / Apps / DevTools / ClassEditor.0.4 / Source / MiscSources.subproj / MiscEmacsText.m < prev    next >
Encoding:
Text File  |  1995-06-07  |  27.7 KB  |  949 lines

  1. /**
  2.  ** EvalText.m
  3.  **
  4.  ** A text subclass which provides emacs key bindings
  5.  ** and "colored" program text. 
  6.  **/
  7.  
  8. #import "MiscEmacsText.h"
  9.  
  10. #import <stdlib.h>
  11. #import <strings.h>
  12. #import <appkit/appkit.h>
  13. #import <defaults/defaults.h>
  14. #import <libc.h>
  15. #import <ctype.h>
  16.  
  17.  
  18. #define ESC    (27)
  19. #define CTRL_SPACE (0)
  20. #define CTRL_A (1)
  21. #define CTRL_B (2)
  22. #define CTRL_C (3)
  23. #define CTRL_D (4)
  24. #define CTRL_E (5)
  25. #define CTRL_F (6)
  26. #define CTRL_K (11)
  27. #define CTRL_L (12)
  28. #define CTRL_N (14)
  29. #define CTRL_O (15)
  30. #define CTRL_P (16)
  31. #define CTRL_S (19)
  32. #define CTRL_V (22)
  33. #define CTRL_W (23)
  34. #define CTRL_X (24)
  35. #define CTRL_Y (25)
  36.  
  37. /**
  38.  ** keyWords[] is a structure used by format: sender to
  39.  ** identify keyWords in transcript text
  40.  **/
  41. static struct 
  42. { char * word ;
  43.    int len ;
  44. } keyWords[] = 
  45.   { {"int",3},
  46.     {"char",4},
  47.     {"float",5},
  48.     {"double",6},
  49.     {"struct",6},
  50.     {"union",5},
  51.     {"long",4},
  52.     {"short",5},
  53.     {"unsigned",8},
  54.     {"auto",4},
  55.     {"extern",6},
  56.     {"register",8},
  57.     {"typedef",7},
  58.     {"static",6},
  59.     {"goto",4},
  60.     {"return",6},
  61.     {"sizeof",6},
  62.     {"break",5},
  63.     {"continue",8},
  64.     {"if",2},
  65.     {"else",4},
  66.     {"for",3},
  67.     {"do",2},
  68.     {"while",5},
  69.     {"switch",6},
  70.     {"case",4},
  71.     {"default",7},
  72.     {"entry",5},  /* is this a keyword in ansi C ? */
  73.     {"const",5},
  74.     {"id",2},
  75.     {"void",4},
  76.     {NULL,0}
  77.   } ;
  78.  
  79.  
  80. /**
  81.  ** Fragments of the following procedures were borrowed from
  82.  ** Lee boynton's LispExample program, then hacked to bits.
  83.  **/
  84.  
  85. static char killedText[10000] ; // buffer for "killed" text...
  86. // since it is statically allocated, this thing could blow up
  87. // if > 10000 chars are written to the kill buffer.  If I had
  88. // I'd make this grow as needed....
  89.  
  90. static BOOL useEmacsBindings = 1 ; 
  91.  
  92. typedef struct {
  93.     id text;
  94.     NXTextBlock *block;
  95. } textInfo;
  96.  
  97. BOOL evalGets(NXStream *aStream, char * aBuf)
  98. { // Procedure to get the next line (or remainder of
  99.   // stream) from a stream. The newline is discarded.  
  100.   // If newline is present, returns YES, else if instead
  101.   // the end of stream is found, returns NO (but aBuf may
  102.   // still contain data).
  103.   int c,i = 0;
  104.   c = NXGetc(aStream) ;
  105.   do
  106.   { if(c == -1) // end of stream
  107.     { aBuf[i] = '\0' ;
  108.       return NO ;
  109.     }
  110.     else if(c == '\n')
  111.     { aBuf[i] = '\0' ;
  112.       return YES ;
  113.     }
  114.     else
  115.     { aBuf[i++] = (char) c ;
  116.       c = NXGetc(aStream) ;
  117.     } 
  118.   } while(1) ;
  119. }
  120.  
  121. static char getPrevious(NXStream *s)
  122. { // NXBGetc - a text stream macro to get the previous character.
  123.   textInfo *info = (textInfo *) s->info;
  124.   NXTextBlock *block = info->block->prior;
  125.   if (!block)
  126.   { return EOF;
  127.   }
  128.   s->buf_base = block->text;
  129.   s->buf_ptr = s->buf_base + block->chars;
  130.   s->offset -= block->chars;
  131.   info->block = block;
  132.   return *(--s->buf_ptr);
  133. }
  134.  
  135. #define NXBGetc(s) \
  136.     (((s)->buf_base == (s)->buf_ptr) ? getPrevious(s) : \
  137.     *(--((s)->buf_ptr)) )
  138.  
  139.  
  140. int findMatchingOpenParen(id text, char thisChar, char thatChar, int curPos)
  141. { int count = 1;
  142.   char ch;
  143.   NXStream *s = [text stream];
  144.   NXSeek(s, curPos, NX_FROMSTART);
  145.   while ((ch = NXBGetc(s)) != EOF)
  146.   { if(ch == thatChar && !(--count))
  147.       return NXTell(s);
  148.     else if (ch == thisChar)
  149.       count++;
  150.   }
  151.   return -1;
  152. }
  153.  
  154.  
  155. int
  156. findMatchingCloseParen(id text, char thisChar, char thatChar, int curPos)
  157. { int count = 1;
  158.   char ch;
  159.   NXStream *s = [text stream];
  160.   NXSeek(s, curPos, NX_FROMSTART);
  161.   while ((ch = NXGetc(s)) != EOF)
  162.   { if(ch == thatChar && !(--count))
  163.       return NXTell(s);
  164.     else if (ch == thisChar)
  165.       count++;
  166.   }
  167.   return -1;
  168. }
  169.  
  170. void highlightChar(id text, int charPosition)
  171. { NXSelPt selStart, selEnd;
  172.   [text getSel:&selStart :&selEnd];
  173.   [text setSel:charPosition :charPosition+1];
  174.   [[text window] flushWindow];
  175.   NXPing();
  176.   [[text window] disableFlushWindow];
  177.   [text setSel:selStart.cp :selEnd.cp];
  178.   [[text window] reenableFlushWindow];
  179. }
  180.  
  181.  
  182. @implementation MiscEmacsText:Text
  183.  
  184.  
  185. // macros which write rtf to a stream called "cookedStream"
  186. // for creating fancy text for various code categories
  187. #define PREPROC NXPrintf(cookedStream,"\\f0%s\\cf0%s ",\
  188.   fontStuff[0][0],fontStuff[0][2])    
  189. #define  DIRECTIVE NXPrintf(cookedStream,"\\f1%s\\cf1%s ",\
  190.   fontStuff[1][0],fontStuff[1][2])    
  191. #define METHODDEF NXPrintf(cookedStream,"\\f2%s\\cf2%s ",\
  192.   fontStuff[2][0],fontStuff[2][2])    
  193. #define COMMENT NXPrintf(cookedStream,"\\f3%s\\cf3%s ",\
  194.   fontStuff[3][0],fontStuff[3][2])    
  195. #define STRING NXPrintf(cookedStream,"\\f4%s\\cf4%s ",\
  196.   fontStuff[4][0],fontStuff[4][2])    
  197. #define  KEYWORD NXPrintf(cookedStream,"\\f5%s\\cf5%s ",\
  198.   fontStuff[5][0],fontStuff[5][2])    
  199. #define NORMAL NXPrintf(cookedStream,"\\f6%s\\cf6%s ",\
  200.   fontStuff[6][0],fontStuff[6][2])    
  201.  
  202. #define TEXTDEFAULTKNT 7 /* number of text categories */
  203.  
  204. - awakeFromNib ;
  205. { // Do some setup work...I am my own delegate
  206.   // [self setDelegate: self] ;
  207.  
  208.     NXAtom textTypes[5];
  209.     
  210.     textTypes[0] = NXRTFPboardType;
  211.     textTypes[1] = NXAsciiPboardType;
  212.     textTypes[2] = NULL;
  213.   
  214.     [self registerForDraggedTypes:textTypes count:2];
  215.     return self;
  216. }
  217.  
  218. - clearMessage: sender ;
  219. { // Clear the message in the transcript.  Called by keydown
  220.   // via a delayed message send.
  221.   // 
  222.   // [window message: ""] ;
  223.   return self ; 
  224. }
  225.  
  226. - format: sender ;
  227. { // Color and set font attributes for text in the
  228.   // transcript.  If sender responds to
  229.   // to the methodList: method, (which must should return a List),
  230.   // then allocates and adds an EvalString with the first line
  231.   // of each method name to this methodlist.
  232.   char textBuf[1024] ;
  233.   char fontStuff[TEXTDEFAULTKNT][3][40], defName[12] ;
  234.   const char *defaults ;
  235.   int i, j, keyWordLen, charNum, braceLevel = 0 ; 
  236.   BOOL inString = NO, inComment = NO, inSlashSlashComment = NO,
  237.        inMethodDef = NO, inPreProc = NO, newline;
  238.   NXStream *rawStream, *cookedStream ;
  239.   id methodList = nil ;
  240.  
  241.     /*
  242.       if([sender respondsTo: @selector(methodList)])
  243.   { methodList = [sender methodList] ;
  244.   } 
  245.       */
  246.        
  247.   rawStream = [self stream] ;
  248.   cookedStream = NXOpenMemory(NULL,0,NX_READWRITE) ;
  249.   NXPrintf(cookedStream,"{\\rtf0\\ansi{\\fonttbl") ;
  250.   // get the font information from defaults database
  251.   for(i = 0 ; i < TEXTDEFAULTKNT ; i++)
  252.   { // Manufacture default name 
  253.     sprintf(defName,"text%1d",i) ;
  254.     defaults = NXGetDefaultValue([NXApp appName],defName) ;
  255.     // parse the default value
  256.     sscanf(defaults,"%s %s %s %s",
  257.        textBuf, fontStuff[i][0], fontStuff[i][1],fontStuff[i][2]) ;
  258.     NXPrintf(cookedStream,"\\f%1d\\fnil %s;", i,textBuf) ;
  259.    }
  260.    NXPrintf(cookedStream,"}\n") ;
  261.    NXPrintf(cookedStream,"{\\colortbl") ;
  262.    for(i = 0 ; i < TEXTDEFAULTKNT ; i++)
  263.    { NXPrintf(cookedStream,fontStuff[i][1]) ;
  264.      NXPrintf(cookedStream,";") ;
  265.    }
  266.    NXPrintf(cookedStream,"}\n") ;
  267.    charNum = 0 ;
  268.    NORMAL ;
  269.    do
  270.    { NXSeek(rawStream, charNum, NX_FROMSTART) ;
  271.      newline = evalGets(rawStream,textBuf) ;
  272.      inString = NO ; // ANSI C forbids newline in string
  273.      // now examine the string, adding rtf directives as required
  274.      for(i = 0 ; textBuf[i] ;) 
  275.      { switch(textBuf[i])
  276.        { case '/':
  277.            if(inString)
  278.               NXPutc(cookedStream,textBuf[i++]) ;
  279.            else if(textBuf[i + 1] == '*' || textBuf[i+1] == '/')
  280.            { if(!inComment)
  281.              { COMMENT ;
  282.                inComment = YES ;
  283.                if(textBuf[i+1] == '/') // behaves specially!
  284.                  inSlashSlashComment = YES ;
  285.              }
  286.              NXPutc(cookedStream,textBuf[i++]) ;
  287.            } 
  288.            else
  289.              NXPutc(cookedStream,textBuf[i++]) ;
  290.            break ;
  291.          case '*':
  292.            if(inComment && textBuf[i+1] == '/' && !inSlashSlashComment)
  293.            { NXPutc(cookedStream,textBuf[i++]) ;
  294.              NXPutc(cookedStream,textBuf[i++]) ;
  295.              if(inMethodDef)
  296.                METHODDEF ;
  297.              else
  298.                NORMAL ;
  299.              inComment = NO ;
  300.            }
  301.            else
  302.              NXPutc(cookedStream,textBuf[i++]) ;
  303.            break ;
  304.          case '"':
  305.             if(inComment || inPreProc)
  306.               NXPutc(cookedStream,textBuf[i++]) ;
  307.             else if(inString) 
  308.             { NXPutc(cookedStream,textBuf[i++]) ;
  309.               if(textBuf[i - 2] != '\\')
  310.               { inString = NO ;
  311.                 NORMAL ;
  312.               }
  313.             }
  314.             else
  315.             { if(i == 0 || textBuf[i - 1] != '\\')
  316.               { inString = YES ;
  317.                 STRING ;
  318.               }
  319.               NXPutc(cookedStream,textBuf[i++]) ;
  320.             }
  321.             break ;
  322.          case '}' :
  323.            if(!inComment && !inString && !inPreProc)
  324.            { if(braceLevel > 0) // don't allow this to go negative
  325.                braceLevel-- ;
  326.            }  
  327.            // must be escaped for rtf
  328.            NXPutc(cookedStream,'\\') ;
  329.            NXPutc(cookedStream,textBuf[i++]) ;
  330.            break ;
  331.          case '\\':
  332.            // must be escaped for rtf
  333.            NXPutc(cookedStream,'\\') ;
  334.            NXPutc(cookedStream,textBuf[i++]) ;
  335.            break ;
  336.          case '{' :
  337.            if(!inComment && !inString && !inPreProc)
  338.              braceLevel++ ;
  339.            if(braceLevel == 1 && inMethodDef)
  340.            { inMethodDef = NO ;
  341.              NORMAL ; 
  342.            }
  343.            NXPutc(cookedStream,'\\') ;
  344.            NXPutc(cookedStream,textBuf[i++]) ;
  345.            break ;
  346.          case '#':
  347.            if(!inComment && i == 0)
  348.            { // a hash in column 0 means a preprocessor line
  349.              PREPROC ;
  350.              inPreProc = YES ;
  351.            }
  352.            NXPutc(cookedStream,textBuf[i++]) ;
  353.            break ;
  354.          case '@':
  355.            if(!inString && !inComment && !inPreProc) 
  356.            { // then this is a compiler directive
  357.              DIRECTIVE ;
  358.              // if((i == 0) && methodList) // if a compiler directive is in
  359.                                       // column 0, put it into the methodList 
  360.             
  361.             //
  362.             //
  363.             //   [methodList addObject: [[EvalString alloc] init: textBuf]] ;
  364.             //
  365.             //
  366.             
  367.              NXPutc(cookedStream,textBuf[i++]) ;
  368.              while(isalnum(textBuf[i]))
  369.                NXPutc(cookedStream,textBuf[i++]) ;
  370.              NORMAL ;
  371.            }
  372.            else
  373.              NXPutc(cookedStream,textBuf[i++]) ;
  374.            break ;
  375.          case '-':
  376.          case '+':
  377.            if(!inComment && !inString && !inPreProc && braceLevel == 0)
  378.            { for(j = 0 ; j < i ; j++)
  379.                if(!isspace(textBuf[j]) && textBuf[j] != '\\') break ;
  380.              if(j == i)
  381.              { // this is not a truly accurate way of finding the beginning of a
  382.                // method def, but it will work "most" of the time...
  383.                inMethodDef = YES ;
  384.                METHODDEF ;
  385.              //
  386.              //
  387.              //  if(methodList)
  388.              //    [methodList addObject: [[EvalString alloc] init: textBuf]] ;
  389.              //
  390.              //
  391.              //
  392.              
  393.              }
  394.            }
  395.            if(textBuf[i] == '-') // must be escaped
  396.              NXPutc(cookedStream,'\\') ;
  397.            NXPutc(cookedStream,textBuf[i++]) ;
  398.            break ;
  399.          case ' ':
  400.          case '\t':
  401.            NXPutc(cookedStream,textBuf[i++]) ;
  402.            break ;
  403.          default:
  404.            if(!inComment && !inString && !inMethodDef && !inPreProc &&
  405.               (i == 0 || !(isalnum(textBuf[i-1]) || textBuf[i-1] == '_')))
  406.            { // check for a keyword
  407.              for(j = 0 ; keyWordLen = keyWords[j].len ; j++)
  408.              { if(!strncmp(&textBuf[i],keyWords[j].word,keyWordLen))    
  409.                { int followChar = i + keyWordLen  ; 
  410.                  int isKeyWord = !(isalnum(textBuf[followChar])
  411.                                    || textBuf[followChar]=='_') ;
  412.                  if(isKeyWord)
  413.                    KEYWORD ;
  414.                  NXPrintf(cookedStream,"%s\n",keyWords[j].word) ;
  415.                  if(isKeyWord)
  416.                    NORMAL ;
  417.                  i += keyWordLen  ;
  418.                  break ;
  419.                }
  420.              }
  421.              if(!keyWordLen) // no keyWord match found
  422.                NXPutc(cookedStream,textBuf[i++]) ;
  423.            }
  424.           else
  425.             NXPutc(cookedStream,textBuf[i++]) ;
  426.           break ;
  427.        }
  428.     }
  429.     // check for a preprocessor continuation line
  430.     if(inPreProc && textBuf[i - 1] != '\\') 
  431.     { inPreProc = NO ;
  432.        NORMAL ;
  433.     }
  434.     // terminate a slash-slash style comment
  435.     if(inSlashSlashComment)
  436.     { inSlashSlashComment = inComment = NO ;
  437.       if(inMethodDef)
  438.         METHODDEF ;
  439.       else
  440.         NORMAL ;
  441.     }
  442.     if(newline) // output a quoted newline
  443.     { NXPutc(cookedStream,'\\') ;
  444.       NXPutc(cookedStream,'\n') ;
  445.     }
  446.     charNum += strlen(textBuf) + 1;
  447.   } while(!NXAtEOS(rawStream)) ;
  448.  
  449.       NXPutc(cookedStream,'}') ;
  450.   NXSeek(cookedStream, 0, NX_FROMSTART) ;
  451.   [self readRichText: cookedStream] ;
  452.   NXCloseMemory(cookedStream,NX_FREEBUFFER) ;
  453.   return self ;
  454. }
  455.  
  456.  
  457. #define MARK_UNSET -1
  458.  
  459. - keyDown:(NXEvent *)theEvent
  460. { // provide some emacs-style key bindings. We make
  461.   // the assumption that we are subview of a scrollview.
  462.   int i ;
  463.   static unsigned short val = '\0', lastChar = '\0', nextChar ;
  464.   static BOOL firstKey = TRUE, definingMacro = NO,
  465.       insertChar, replayingMacro = NO ;
  466.   static long mark = MARK_UNSET ; 
  467.   NXSelPt selStart, selEnd;
  468.   static NXStream * macroStream ;
  469.   if(!useEmacsBindings)
  470.     return [super keyDown: theEvent] ;
  471.  
  472.   if(firstKey) // make sure killedText is empty
  473.   { killedText[0] = '\0' ;
  474.     firstKey = FALSE ;
  475.     macroStream = NXOpenMemory(NULL, 0, NX_READWRITE) ;
  476.   }
  477.   [self getSel:&selStart :&selEnd];
  478.   do
  479.   { lastChar = val ; // val was set in "previous" call of keyDown..
  480.     val = theEvent->data.key.charCode + (theEvent->data.key.charSet << 8);
  481.     if(definingMacro)
  482.       NXPutc(macroStream,theEvent->data.key.charCode) ;
  483.     insertChar = YES ;
  484.     switch ((unsigned char) val)
  485.     { case ESC: // don't want ESC to cause a beep
  486.         insertChar = NO ;
  487.         break ;
  488.       case '<':
  489.         if(lastChar != ESC)
  490.           break ;
  491.       case 0xa3:  // ALT-< or ESC <
  492.         { [self setSel: 0 :0] ;
  493.           [self scrollSelToVisible] ;
  494.           insertChar = NO ;
  495.         }
  496.         break ;
  497.       case '>':
  498.        if(lastChar != ESC)
  499.          break ;
  500.       case 0xb3: // ALT-> or ESC >
  501.        { i = [self textLength] ; --i ;
  502.           [self setSel: i :i] ;
  503.          [self scrollSelToVisible] ;
  504.          insertChar = NO ;   
  505.        }
  506.        break ;
  507.      case '(':
  508.        if((lastChar == CTRL_X) && !definingMacro)
  509.        { // start defining macro
  510.          // [window message: "Defining kbd macro..."] ;
  511.          definingMacro = YES ;
  512.          NXSeek(macroStream, 0, NX_FROMSTART) ;
  513.          insertChar = NO ;
  514.        }
  515.        break ;
  516.      case ')':
  517.        if((lastChar == CTRL_X) && definingMacro)
  518.        {  // [window message: "Keyboard macro defined."] ;
  519.           // [self perform: @selector(clearMessage:) with: self
  520.           //  afterDelay: 1500 cancelPrevious: YES] ;
  521.           definingMacro = NO ;
  522.           NXSeek(macroStream, -2, NX_FROMCURRENT) ; // seek back 2 to remove ctrl_X )
  523.           NXPutc(macroStream,'\0') ; // null-terminate the stream
  524.           insertChar = NO ; 
  525.        }
  526.        else      
  527.        { i = findMatchingOpenParen(self,')','(',selEnd.cp);
  528.          [super keyDown:theEvent];
  529.          if (i >= 0)
  530.            highlightChar(self,i);
  531.         insertChar = NO ;
  532.         }
  533.        break ;
  534.      case '}':
  535.        i = findMatchingOpenParen(self,'}','{',selEnd.cp);
  536.        [super keyDown:theEvent];
  537.        if(i >= 0)
  538.          highlightChar(self,i);
  539.        insertChar = NO ;
  540.        break ;
  541.      case ']':
  542.        i = findMatchingOpenParen(self,']','[',selEnd.cp);
  543.        [super keyDown:theEvent];
  544.        if (i >= 0)
  545.          highlightChar(self,i);
  546.        insertChar = NO ;
  547.        break ;
  548.      case 'e':
  549.        if(lastChar == CTRL_X)
  550.        { if(!definingMacro)
  551.          { // replay macro
  552.            replayingMacro = YES ;
  553.            NXSeek(macroStream,0L,NX_FROMSTART) ;
  554.            insertChar = NO ;
  555.          }
  556.          else
  557.            NXSeek(macroStream, -1, NX_FROMCURRENT) ; // seek back 1 to remove ctrl_X 
  558.        }
  559.        break ;
  560.      case 'i':
  561.        if(lastChar == CTRL_X)
  562.        { if(!definingMacro)
  563.          { // insert a file into the text
  564.            int rval ;
  565.            id openPanel = [OpenPanel new] ;
  566.            [openPanel allowMultipleFiles: NO] ;
  567.            rval = [openPanel runModalForTypes: NULL] ;
  568.            if(rval == NX_OKTAG)
  569.            { NXStream *fileStream ;
  570.              char fName[2048] ;
  571.              sprintf(fName,"%s/%s",[openPanel directory],*[openPanel filenames]) ;
  572.              if((fileStream = NXMapFile(fName,NX_READONLY)) != NULL)
  573.              { int len,unused ;
  574.                char *buf ;
  575.                // [window message: "Inserting file..."] ;
  576.                NXGetMemoryBuffer(fileStream,&buf,&len,&unused) ;
  577.                [self replaceSel: buf length: len] ;
  578.                // [window message: ""] ;
  579.              }
  580.              else
  581.                NXRunAlertPanel("Eval","Couldn't insert file: %s",NULL,NULL,NULL,fName) ;               
  582.            }
  583.          }
  584.          insertChar = NO ;
  585.        }
  586.        break ;
  587.  
  588.      case 'k': // CTRL_X k = close window
  589.        if(lastChar == CTRL_X)
  590.        { [window performClose: self] ;
  591.          insertChar = NO ;
  592.        }
  593.        break ;       
  594.      case 's': 
  595.        if(lastChar == CTRL_X)
  596.        { [window save: self] ;
  597.          insertChar = NO ;
  598.        }
  599.        break ;
  600.      case 'v':   
  601.        if(lastChar != ESC)
  602.          break ;
  603.      case 0xd6: // ALT-v or ESC v
  604.        { // move up one screenful
  605.           NXPoint scrollPoint = {0.0,0.0} ;
  606.           NXRect  clipRect ;
  607.           [self convertPoint: &scrollPoint fromView: [superview superview]] ;
  608.           // get the clipview's bounds
  609.           [superview getBounds: &clipRect] ;
  610.           scrollPoint.y -= clipRect.size.height ; // scroll up
  611.           [self scrollPoint: &scrollPoint] ;  
  612.           insertChar = NO ;
  613.         }
  614.         break ;    
  615.      case 'w':
  616.        if(lastChar != ESC)
  617.          break ;
  618.      case 0xc8: // ALT-w or ESC w
  619.        { int subStringLength ;
  620.          if(mark == MARK_UNSET)
  621.            break ; 
  622.          [self getSel:&selStart :&selEnd];
  623.          if(selStart.cp < mark)
  624.            [self getSubstring: killedText
  625.             start: selStart.cp length: subStringLength = mark - selStart.cp] ;
  626.          else
  627.            [self getSubstring: killedText
  628.             start: mark length: subStringLength = selStart.cp - mark] ;
  629.          killedText[subStringLength] = '\0' ;
  630.          insertChar = NO ;
  631.        }
  632.        break ;
  633.      case CTRL_SPACE: // set mark
  634.        [self getSel:&selStart :&selEnd];
  635.        mark = selStart.cp ;
  636.        insertChar = NO ;
  637.        break ;
  638.      case CTRL_A: // beginning of line
  639.        [self getSel:&selStart :&selEnd];
  640.        i = [self lineFromPosition:selStart.cp] ;
  641.        i = [self positionFromLine: i] ;
  642.        [self setSel:i :i];
  643.        insertChar = NO ;
  644.        break ;
  645.      case CTRL_C: 
  646.        if(lastChar == CTRL_X)
  647.        { [NXApp terminate: self] ;
  648.          insertChar = NO ;
  649.        }
  650.        break ;
  651.      case CTRL_E: // end of line
  652.        [self getSel:&selStart :&selEnd];
  653.        i = [self lineFromPosition: selStart.cp] ;
  654.        i = [self positionFromLine: i + 1] ; // beginning of next line
  655.        if(i == -1) // then there is no next line...
  656.          i = [self textLength] + 1 ;
  657.        [self setSel:i -1 :i - 1];
  658.        insertChar = NO ;
  659.        break ;
  660.      case CTRL_B:// backwards one char
  661.        [self getSel:&selStart :&selEnd];
  662.        if(selStart.cp > 1)
  663.          [self setSel:selStart.cp - 1 :selStart.cp -1];
  664.        else
  665.          NXBeep();
  666.        insertChar = NO ;
  667.        break ;
  668.      case CTRL_F: // CTRL_X CTRL_F = open file, CTRL_F forwards one char
  669.        if(lastChar == CTRL_X)
  670.        { [NXApp open: self] ;
  671.        }
  672.        else
  673.        { [self getSel:&selStart :&selEnd];
  674.          if(selEnd.cp < [self textLength])
  675.            [self setSel:selEnd.cp+1 :selEnd.cp+1];
  676.          else
  677.            NXBeep();
  678.         }
  679.         insertChar = NO ;
  680.         break ;
  681.      case CTRL_D: // delete char under cursor
  682.        [self getSel:&selStart :&selEnd];
  683.        [self setSel:selEnd.cp :selEnd.cp+1];
  684.        [self replaceSel:""];
  685.        [self setSel:selStart.cp :selStart.cp] ;
  686.        insertChar = NO ;
  687.        break ;
  688.      case CTRL_K: // clear to end of line, place killed text in killedText 
  689.      // if line is empty, then delete the newline (and place at end of killedText)
  690.      { int currentEnd = 0 ;
  691.        [self getSel:&selStart :&selEnd];
  692.        i = [self lineFromPosition: selStart.cp] ;
  693.        i = [self positionFromLine: i + 1] ; // i is index of beginning of next line
  694.        if(i == -1) // then there is no next line...
  695.        { i = [self textLength] ;
  696.        }
  697.        else // there is another line
  698.        { if(i - selStart.cp > 1) // if line is not empty,
  699.          i-- ;  // don't include the newline.
  700.        }
  701.        // successive CTRL_K's append to killedText ; any other char
  702.        // interrupts the sequence
  703.        if(lastChar == CTRL_K)
  704.          currentEnd = strlen(killedText) ;
  705.        [self getSubstring:  &killedText[currentEnd]
  706.          start:selStart.cp length:i- selStart.cp] ;
  707.        killedText[i - selStart.cp + currentEnd] = '\0' ;
  708.        [self setSel:selStart.cp :i];
  709.        [self replaceSel:""];
  710.        [self setSel:selStart.cp :selStart.cp] ;
  711.        insertChar = NO ;
  712.        break ;
  713.      }
  714.      case CTRL_L: // center the current selection
  715.      { // get the selection's location in the scrollview's coordinates
  716.        NXPoint loc, scrollPoint = {0.0,0.0} ;
  717.        NXRect  scrollRect ;
  718.        float delta ;
  719.        id sView = [[self superview] superview] ;
  720.        [self getSel:&selStart :&selEnd];
  721.        loc.x = selStart.x ;
  722.        loc.y = selStart.y ;
  723.        [self convertPoint: &loc toView: sView] ;
  724.        // get the scrollview's bounds
  725.        [sView getBounds: &scrollRect] ;
  726.        // how far is loc.y from middle of scrollview?...reset delta to this value
  727.        delta = scrollRect.size.height / 2.0 - loc.y ;
  728.        [sView convertPoint: &scrollPoint toView: self] ;
  729.        scrollPoint.y -= delta ;
  730.        [self scrollPoint: &scrollPoint] ;  
  731.        insertChar = NO ;
  732.        break ;     
  733.      }
  734.      case CTRL_O: // "open" a line...i.e. add blank line before current
  735.        [self getSel:&selStart :&selEnd];
  736.        [self setSel: selStart.cp :selStart.cp] ;
  737.        [self replaceSel: "\n"] ;            
  738.        [self setSel: selStart.cp :selStart.cp] ;
  739.        insertChar = NO ;
  740.        break ;
  741.      case CTRL_N: // move directly up or down one line
  742.      case CTRL_P: // move directly up one line...fake up arrow
  743.         { NXEvent fakeEvent ;
  744.           bcopy((char *) theEvent,(char *) &fakeEvent,sizeof(NXEvent)) ;
  745.           fakeEvent.data.key.charCode = val == CTRL_N ? 175 :173 ;
  746.           fakeEvent.data.key.charSet = 1 ;
  747.           fakeEvent.flags = NX_NUMERICPADMASK ;
  748.           [super keyDown: &fakeEvent] ;
  749.           insertChar = NO ;
  750.          }
  751.         break ;
  752.      case CTRL_S:  // ctrl x ctrl s = save
  753.        if(lastChar == CTRL_X)
  754.        { [window save: self] ;
  755.          insertChar = NO ;
  756.        }
  757.        else
  758.        { 
  759.            //
  760.         //
  761.         //
  762.         // extern id finderObject ; // declared in Finder.m
  763.         //
  764.         // [finderObject doFind: self] ; 
  765.         //
  766.         //
  767.        }
  768.        break ;
  769.       case CTRL_V: // move down one screenful
  770.        {  NXPoint scrollPoint = {0.0,0.0} ;
  771.           NXRect  clipRect ;
  772.  
  773.           [self convertPoint: &scrollPoint fromView: [superview superview]] ;
  774.           // get the clipview's bounds
  775.           [superview getBounds: &clipRect] ;
  776.           scrollPoint.y += clipRect.size.height ; // scroll down
  777.           [self scrollPoint: &scrollPoint] ;  
  778.           insertChar = NO ;
  779.           break ;    
  780.         }
  781.       case CTRL_W: // delete text from point to mark ; add to killedText
  782.         if(mark == MARK_UNSET)
  783.           break ;
  784.         [self getSel:&selStart :&selEnd];
  785.         if(selStart.cp < mark)
  786.         {  [self getSubstring: killedText
  787.             start: selStart.cp length:mark - selStart.cp] ;
  788.            [self setSel: selStart.cp :mark] ;
  789.         }
  790.         else
  791.         { [self getSubstring: killedText
  792.             start: mark length: selStart.cp - mark] ;
  793.           [self setSel: mark :selStart.cp] ;
  794.         } 
  795.         [self replaceSel: ""] ;          
  796.         insertChar = NO ;
  797.         break ;
  798.        case CTRL_X: // don't want CTRL_X to cause a beep
  799.         insertChar = NO ;
  800.         break ;
  801.       case CTRL_Y: // yank killedText 
  802.         [self getSel:&selStart :&selEnd];
  803.         [self setSel:selStart.cp :selStart.cp] ;
  804.         [self replaceSel: killedText] ;
  805.         insertChar = NO ;
  806.         break ;
  807.       default:
  808.         break;
  809.     }
  810.     if(insertChar)
  811.       [super keyDown: theEvent] ;
  812.     if(replayingMacro)
  813.     { nextChar = NXGetc(macroStream) ;
  814.       if(nextChar == '\0')
  815.       { replayingMacro = NO ;
  816.         NXPing() ;
  817.       }
  818.       else
  819.         theEvent->data.key.charCode = nextChar ;
  820.     }
  821.   } while(replayingMacro) ;
  822.   return self ;
  823. }
  824.  
  825.  
  826. - mouseDown: (NXEvent *) anEvent ;
  827. { // augment double-click behavior to
  828.   // check for bracketed structures and quoted
  829.   // items. augment command-click behaviour to
  830.   // select between backquotes.
  831.   NXSelPt start, end ;
  832.   char openers[] = "{([<\"'/`" ;
  833.   char closers[] = "})]>\"'/`" ;
  834.   char c,d, *place ;
  835.   int otherPos ;
  836.   NXStream *aStream ;
  837.   if(anEvent->data.mouse.click == 1)
  838.     [super mouseDown: anEvent] ;
  839.   else if(anEvent->data.mouse.click == 2)
  840.   { aStream = [self stream] ;
  841.     [self getSel: &start :&end] ;
  842.     if(start.cp > 0)
  843.     { NXSeek(aStream,start.cp - 1,NX_FROMSTART) ;
  844.       c = NXGetc(aStream) ;
  845.       if(place = index(closers,c)) // we have right side of a structure
  846.       {  d = openers[(int) place - (int) closers] ;// grab matching char
  847.          if( (otherPos = findMatchingOpenParen(self, c, d, start.cp -1))
  848.            != -1)
  849.         { if(c == '`') // put selection "inside" ` ... ` pair
  850.             [self setSel: otherPos + 1 :start.cp - 1] ;
  851.           else
  852.             [self setSel: otherPos :start.cp] ;
  853.           return self ;
  854.         }
  855.         else
  856.           [super mouseDown: anEvent] ;
  857.       }
  858.       else
  859.       { NXSeek(aStream,start.cp,NX_FROMSTART) ;
  860.         c = NXGetc(aStream) ;
  861.         if(place = index(openers,c)) // we have left side of a structure
  862.         { d = closers[(int) place - (int) openers] ;
  863.           if( (otherPos = findMatchingCloseParen(self, c, d, start.cp +1))
  864.            != -1)
  865.           { if(c == '`') // put selection "inside" ` ... ` pair
  866.              [self setSel: start.cp +1 :otherPos-1] ;
  867.            else
  868.              [self setSel: start.cp :otherPos] ;
  869.           }
  870.         }
  871.         else
  872.           [super mouseDown: anEvent] ;
  873.       }
  874.     }
  875.   }
  876.   else // deal with triple, quadruple, etc.  clicks here...
  877.     [super mouseDown: anEvent] ; 
  878.   if((anEvent->data.mouse.click == 1) &&
  879.      (anEvent->flags & NX_COMMANDMASK))
  880.   { // command click == search back for backquote (or beginning of text)
  881.     // forward for backquote (or end ot text), then select. 
  882.     int from, to ;
  883.     aStream = [self stream] ;
  884.     [self getSel: &start :&end] ;
  885.     if(start.cp > 0)
  886.     { from = start.cp - 1 ;
  887.       NXSeek(aStream,from,NX_FROMSTART) ;
  888.       while(from >= 1 && ((c = NXBGetc(aStream)) != '`'))
  889.         from-- ;
  890.       to = start.cp ;
  891.       NXSeek(aStream,to,NX_FROMSTART) ;
  892.       while(!NXAtEOS(aStream) && ((c = NXGetc(aStream)) != '`'))
  893.         to++ ;
  894.       [self setSel: from :to] ;
  895.     }
  896.   }
  897.   return self ;
  898. }
  899.  
  900. /*
  901. - textDidGetKeys: sender isEmpty: (BOOL) flag
  902. { [window setDocEdited: YES] ;     
  903.   return self ;
  904. }
  905. */
  906.  
  907. - useEmacsBindings: (BOOL) YESorNO ;
  908. { useEmacsBindings = YESorNO ;
  909.   return self ;
  910. }
  911.  
  912. @end
  913.  
  914. @implementation MiscEmacsText(Dropping)
  915.  
  916. - (BOOL)readDragData:(Pasteboard *)pboard
  917. {            
  918.     return YES;
  919. }
  920.  
  921. - (NXDragOperation)draggingEntered:(id <NXDraggingInfo>)sender
  922. {
  923.     NXDragOperation sourceMask;
  924.  
  925.     sourceMask = [sender draggingSourceOperationMask];
  926.  
  927.     if (sourceMask & NX_DragOperationCopy)
  928.         return NX_DragOperationCopy;
  929.  
  930.     return NX_DragOperationNone;
  931. }
  932.  
  933. - (BOOL)performDragOperation:(id <NXDraggingInfo>)sender
  934. {
  935.     return YES;
  936. }
  937.  
  938. - (BOOL)prepareForDragOperation:(id <NXDraggingInfo>)sender
  939. {
  940.     return YES;
  941. }
  942.  
  943. - concludeDragOperation:(id <NXDraggingInfo>)sender
  944. {
  945.     [self pasteFrom:[sender draggingPasteboard]];
  946.     return self;
  947. }
  948.  
  949. @end